package com.my.ddd_sample_collections.aggregate_change_tracking.repository_impl;

import com.my.ddd_sample_collections.aggregate_change_tracking.abstract_domain.AbstractItemDO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.MyAggregateDAO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.SimpleDAO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.po.ComplexPO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.po.ItemPO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.po.MyAggregatePO;
import com.my.ddd_sample_collections.aggregate_change_tracking.dao.po.SimplePO;
import com.my.ddd_sample_collections.aggregate_change_tracking.domain.*;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository.MyAggregateRepository;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository_framework.DomainObject;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository_framework.DomainObjectUtils;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository_framework.DeletedDomainObjectKey;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository_impl.dto.InputDTO;
import com.my.ddd_sample_collections.aggregate_change_tracking.repository_impl.dto.MyAggregateDTO;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
public class MyAggregateRepositoryImpl implements MyAggregateRepository<MyAggregateDO> {
    
    @Autowired
    MyAggregateDAO  myAggregateDAO;
    @Autowired
    private SimpleDAO simpleDAO;
    
    
    @Override
    public MyAggregateDO create(InputDTO inputDto) {
        return createAggregateRoot((MyAggregateDTO)inputDto);
    }

    @Override
    public MyAggregateDO load(Identity id) {

        //调用DAO从数据库加载
        MyAggregateDO root = loadAggregateRoot(id);

        //TODO 深层标记为unchanged状态，由平台来调用会比较友好（避免由开发来写）
        DomainObjectUtils.deepMarkUnchanged(root);

        return root;
    }


    @Override
    public void save(MyAggregateDO root) {
        if (root.isNew()) {
            saveNew(root);
        }else if(root.isDeleted()){
            deleteAll(root);
        } else {
            update(root);
        }

    }

    /**
     * 业务开发人员根据自己对聚合和数据库的关系的了解，以及聚合的变更情况，决定如何更新数据库。
     * 这一部分会比较复杂，需要设计一套api来辅助开发人员
     * @param root
     */
    private void update(MyAggregateDO root) {
        if (root.isChanged()){
            //update根实体相关的表
            //根据修改的字段update相关的表
            Set<String> changedFieldNames = root.getChangedFields();
            
            MyAggregatePO rootPO = new MyAggregatePO();
            changedFieldNames.forEach(fieldName -> {
                try {
                    PropertyUtils.setProperty(rootPO, fieldName, PropertyUtils.getProperty(root, fieldName));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            
            myAggregateDAO.update(rootPO);
        }
        
        //获取删除的实体，并删除数据库记录（如果开发很确定业务上不会删除任何数据，此处可以不处理）
        Map<DeletedDomainObjectKey, DomainObject> deleteObjects = root.getDeleteChildObjects();
        //TODO 逐个删除
        deleteObjects.forEach((key, value)->{
            doDelete(key, value);
        });
        
        //开发人员已经知道root的内部结构，编写代码遍历所有内部实体，然后根据实体的变更状态进行处理
        //下级实体变更的字段
        //TODO 处理 ComplexDO
        if (root.getComplex() != null || root.getComplex().isChanged()) {
            Set<String> complexChangedFields = root.getComplex().getChangedFields();

        }
        //其他内部实体的变更字段

    }
    
    private void doDelete(DeletedDomainObjectKey key, DomainObject entity) {
        if (entity instanceof ComplexDO) {
            deleteComplexDO(key ,(ComplexDO)entity);
//         TODO 实现其他实体的删除逻辑
//        } else if (entity instanceof xxxx) {
//            deleteXXXDO();
        }
    }
    
    private void deleteComplexDO(DeletedDomainObjectKey key, ComplexDO entity) {
        //TODO 调用DAO删除（如果entity中的信息不够，还可以通过 key.getParent获取上级实体的信息）
        //        comlexDAO.delete
    }
    
    private void deleteAll(MyAggregateDO root) {
        //TODO 删除聚合
        //TODO 同时删除内部子实体（根实体删除，子实体应该也删除）
    }

    private void saveNew(MyAggregateDO root) {
        //TODO 保存新建的聚合
        //myAggregateDAO.insert(root);
        
        //TODO 同时保存下级子实体（应该不存在根实体是新建，子实体不是新建的情况）
        
    }


    private MyAggregateDO createAggregateRoot(MyAggregateDTO inputDto) {
        //TODO 实现create do from dto（修改下面代码数据从inputDto中取）
        return MyAggregateDO.builder()
                .bar("bar")
                .foo("foo")
//                .simple(simple)
//                .complex(complex)
//                .items(items)
//                .simpleItems(simpleItems)
//                .arrayItems(arrayItem)
                .myExtendedProperty("myExt")
                .build();
    }

    private MyAggregateDO loadAggregateRoot(Object id) {
        //TODO 实现 load aggregate from database（下面代码只是举例，不完整）
        
        //从DAO查找PO
        MyAggregatePO myAggregatePO = myAggregateDAO.findById("id");
        SimplePO simplePO = simpleDAO.findById("xxx");
        ComplexPO complexPO = null; //TODO 实现从DB加载
        List<ItemPO> itemsPo = null; //实现从DB加载
        //TODO 余下PO的实现待完成。。。
        
        //通过PO创建DO
        SimpleDO simpleDO = SimpleDO.builder().email(simplePO.getEmail()).street(simplePO.getStreet()).build();
        ComplexDO complexDO = ComplexDO.builder()
//                .foo()//TODO 从PO中获取数据
                .build();
        List<ItemDO> items = new ArrayList<>(); //TODO 从itemsPO构件
        
        //组装rootDO
        MyAggregateDO root = MyAggregateDO.builder()
                .bar(myAggregatePO.getBar())
                .foo(myAggregatePO.getFoo())
                .simple(simpleDO)
                .complex(complexDO)
//                .items(items) //TODO DomainObject集合不能通过builder初始化
//                .simpleItems(simpleItems)
//                .arrayItems(arrayItem)
                .myExtendedProperty("myExt")
                .build();
        //
        root.getItems().addAll(items);
//        List<AbstractItemDO> aa = new ArrayList<>();
//        aa.add(items.get(0));
        
        //FIXME 这是设置的版本号的一个方案（优点：比较容易实现，DO不用依赖DO；缺点：需要业务感知，但不复杂），另外一种方案是做到AbstractDO的构造函数中（缺点：DO依赖了PO，优点：不用业务感知设置版本号的动作）
        DomainObjectUtils.setPOVersions(root, itemsPo, myAggregatePO, simplePO);
        return root;
    }
    


}
